package org.fastsyncer.core.service.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.fastsyncer.common.DefaultThreadFactory;
import org.fastsyncer.common.constant.CommonConstant;
import org.fastsyncer.common.util.ApplicationUtil;
import org.fastsyncer.core.service.TokenService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service("TokenService")
public final class TokenServiceImpl implements TokenService {

    private static final Logger logger = LoggerFactory.getLogger(TokenServiceImpl.class);

    // 缓存Token信息
    private static final Map<String, Node> map = new ConcurrentHashMap<>();

    // 会话失效时间（毫秒）
    private static final Integer MAX_AGE = Integer.parseInt(ApplicationUtil.getKey(CommonConstant.CONFIG_INTERCEPTOR_SESSION_MAX_AGE)) * 1000;

    // 延迟多少秒后执行轮询任务
    private static final long EXPIRED_INITIALDELAY = 0;

    // 轮询检测过期数据频率（秒）
    private static final long EXPIRED_PERIOD = 5;

    /**
     * 线程池回收时间（毫秒）,如果在1分钟之内缓存数据仍然为空则回收线程池
     */
    private static final long MAX_EXPIRED_THREAD_TIME = 60 * 1000;

    // 创建一个单线程调度线程池
    private ScheduledExecutorService pool;

    // 线程池回收时间
    private Long expiredThreadTime;

    // 线程池运行状态
    private volatile boolean isRunning = false;

    private synchronized void start() {
        if (!isRunning) {
            pool = Executors.newSingleThreadScheduledExecutor(new DefaultThreadFactory("CleanupToken"));
            // 间隔实行定时任务
            pool.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    // 执行清理任务
                    clean();
                    // 如果缓存的数据长时间为空,则回收线程池
                    recover();
                }
            }, EXPIRED_INITIALDELAY, EXPIRED_PERIOD, TimeUnit.SECONDS);
            isRunning = true;
        }
    }

    private synchronized void stop() {
        if (isRunning) {
            // 不管任务是否在执行,立即结束
            pool.shutdownNow();
            isRunning = false;
        }
    }

    private void recover() {
        // 检查到缓存为空,判断是否回收
        if (map.isEmpty()) {
            long timeMillis = System.currentTimeMillis();
            // 记录回收时间, 首次发现空,后面逻辑不需要再判断,直接返回
            if (null == expiredThreadTime) {
                expiredThreadTime = timeMillis + MAX_EXPIRED_THREAD_TIME;
                return;
            }
            // 判断超过回收时间点
            if (timeMillis > expiredThreadTime && isRunning) {
                stop();
            }
        } else {
            if (null != expiredThreadTime) {
                expiredThreadTime = null;
            }
        }
    }

    /**
     * 由于该任务是单线程调度,不存在并发竞争关系
     */
    private void clean() {
        // 找出过期数据
        List<String> list = null;
        long expiredTime = System.currentTimeMillis();
        for (Map.Entry<String, Node> obj : map.entrySet()) {
            // 如果该值过期
            if (expiredTime > obj.getValue().getTime()) {
                if (null == list) {
                    list = new ArrayList<>();
                }
                list.add(obj.getKey());
            }
        }

        // 清理过期数据
        if (null != list && !list.isEmpty()) {
            for (String k : list) {
                map.remove(k);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("doClear>remove keys:" + list.toString());
                logger.debug("Token size:" + map.size());
            }
            list = null;
        }
    }

    @Override
    public String get(String key) {
        Node node = map.get(key);
        return null == node ? null : node.getValue();
    }

    @Override
    public void put(String key, String value) {
        // 给值设置默认有效期
        map.put(key, new Node(value, MAX_AGE + System.currentTimeMillis()));
        if (logger.isDebugEnabled()) {
            logger.debug("put>key:" + key + ",value:" + value);
        }

        // 检查线程池是否启动
        if (!isRunning) {
            start();
        }
    }

    class Node {
        private String value;

        private long time;

        public Node() {
            super();
        }

        public Node(String value, long time) {
            super();
            this.value = value;
            this.time = time;
        }

        public String getValue() {
            return value;
        }

        public long getTime() {
            return time;
        }
    }
}
